package org.midy.base;

import org.midy.infrastructure.tools.Logger;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

public class ConnectClient {
    /* 参数 */
    private static final Charset UTF8 = StandardCharsets.UTF_8;
    private String name;
    private String ip;
    private int port;
    private int connTimeout;
    private int readTimeout;
    private long lastTimestamp = System.currentTimeMillis();
    /* 组件 */
    private Socket client;
    private InputStream inputStream;
    private OutputStream outputStream;

    public static ConnectClient build(String ip, int port, int connTimeout, int readTimeout) {
        ConnectClient connectClient = new ConnectClient();
        connectClient.ip = ip;
        connectClient.port = port;
        connectClient.connTimeout = connTimeout * 1000;
        connectClient.readTimeout = readTimeout * 1000;
        connectClient.name = String.format("IP[%s] PORT[%d] HASH[%d]", connectClient.ip, connectClient.port, connectClient.hashCode());
        return connectClient;
    }

    public static byte[] generateHeader(int preLength, int body) {
        char[] chars = new char[preLength];
        String bodyStr = String.valueOf(body);
        int off = preLength - 1;
        for (int idx = bodyStr.length() - 1; idx >= 0; idx--) {
            chars[off--] = bodyStr.charAt(idx);
        }
        while (off >= 0) {
            chars[off--] = '0';
        }
        return new String(chars).getBytes(UTF8);
    }

    public void send(int preLength, byte[] request) throws IOException {
        if (request == null) {
            return;
        }

        try {
            checkSocket();
            byte[] header = generateHeader(preLength, request.length);
            outputStream.write(header);
            outputStream.write(request);
        } catch (Throwable t) {
            checkClose();
            throw t;
        } finally {
            updateLastTimestamp();
        }
    }

    public byte[] receive(int preLength, long timeout) throws IOException {
        try {
            long timeoutLimit = System.currentTimeMillis() + timeout * 1000;
            byte[] header = new byte[preLength];
            int off = 0;
            while ((System.currentTimeMillis() < timeoutLimit) && (off = (inputStream.read(header, off, preLength - off) + off)) < preLength) {
                Logger.info(" read head size: " + off);
            }

            if (off < header.length) {
                throw new RuntimeException("读超时1");
            }

            Integer msgSize = new Integer(new String(header, UTF8));
            if (msgSize <= 0) {
                return null;
            }

            byte[] body = new byte[msgSize];
            off = 0;
            while ((off = (inputStream.read(body, off, msgSize - off) + off)) < msgSize) {
                Logger.info(" read body size: " + off);
            }

            if (off < body.length) {
                throw new RuntimeException("读超时2");
            }

            updateLastTimestamp();
            return body;
        } catch (Throwable t) {
            checkClose();
            throw t;
        } finally {
            updateLastTimestamp();
        }
    }

    public Socket checkSocket() {
        if (client == null) {
            return initSocket();
        } else if (client.isClosed() || client.isInputShutdown() || client.isOutputShutdown() || !client.isConnected()) {
            checkClose();
            return initSocket();
        }
        return client;
    }

    /* 关闭连接 */
    public void checkClose() {
        Socket record = client;
        try {
            if (client != null && !client.isClosed()) {
                try {
                    client.close();
                } catch (IOException e) {
                    Logger.warn("close Socket Exception", e);
                }
            }

        } finally {
            Logger.info(" close Socket: %d", record == null ? -1 : record.hashCode());

            if(client != null) {
                Logger.info("close Socket[%d]", client.hashCode());
                client = null;
            }
            inputStream = null;
            outputStream = null;

        }
    }

    public Socket initSocket() {
        if (client == null) {
            InetSocketAddress inetSocketAddress = new InetSocketAddress(ip, port);
            client = new Socket();
            try {
                client.setKeepAlive(true);                      // 保持会话连接
                client.connect(inetSocketAddress, connTimeout); // 连接超时时间
                client.setSoTimeout(readTimeout);        // 读超时时间
                outputStream = client.getOutputStream();
                inputStream = client.getInputStream();
            } catch (IOException e) {
                checkClose();
                throw new RuntimeException(String.format("[ip:%s, port:%S] 连接超时!", ip, port), e);
            }
        }
        updateLastTimestamp();
        Logger.info("创建新的网络连接[IP: %s, PORT: %d]", ip, port);
        return client;
    }

    public void updateLastTimestamp() {
        this.lastTimestamp = System.currentTimeMillis();
    }

    public long lastTimestamp() {
        return this.lastTimestamp;
    }

    public String getName() {
        return name;
    }

    public String socketHash() {
        if (client != null) {
            return String.valueOf(client.hashCode());
        } else {
            return "";
        }
    }
}
